/*
 * Copyright 2019-2025 the original author or authors.
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 * https://www.apache.org/licenses/LICENSE-2.0
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package com.iiifi.kite.lock.core;

import java.util.Random;
import java.util.UUID;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.LockSupport;

import com.iiifi.kite.core.spring.SpringContextUtils;
import com.iiifi.kite.lock.redis.RedisLockClient;

import lombok.extern.slf4j.Slf4j;

/**
 * KiteLock
 *
 * @author kite@iiifi.com 花朝
 */
@Slf4j
public class KiteLock implements Lock {
    private Thread thread;
    private String key;
    private int unlockRetry;
    private UUID id = UUID.randomUUID();
    private Random random = new Random();
    private RedisLockClient redisLock;

    public static KiteLock create(String key) {
        return create(key, 1);
    }

    /**
     * @param key 当前服务的别名
     * @param unlockRetry 解锁重试次数
     * @return KiteLock
     */
    public static KiteLock create(String key, int unlockRetry) {
        return new KiteLock(key, unlockRetry);
    }

    private KiteLock(String key, int unlockRetry) {
        this.key = key;
        this.unlockRetry = unlockRetry;
        this.redisLock = SpringContextUtils.getBean(RedisLockClient.class);
    }

    /**
     * 加锁
     *
     * @param expire redis key timeout
     * @param timeUnit the time unit of the timeout argument
     * @throws InterruptedException
     */
    @Override
    public void lock(String requestId, long expire, TimeUnit timeUnit) throws InterruptedException {
        if (expire <= 0L) {
            throw new IllegalArgumentException("expire time least gt zero");
        }
        boolean result;
        for (;;) {
            result = redisLock.lock(key, requestId, expire, timeUnit);
            if (result) {
                thread = Thread.currentThread();
                return;
            } else {
                Thread.sleep(random.nextInt(10));
            }
        }
    }

    @Override
    public boolean tryLock(String requestId, long expire, TimeUnit timeUnit) {
        boolean result = redisLock.lock(key, requestId, expire, timeUnit);
        if (result) {
            thread = Thread.currentThread();
            return true;
        }
        return false;
    }

    @Override
    public boolean tryLock(String requestId, long timeout, long expire, TimeUnit timeUnit) {
        if (expire <= 0L) {
            throw new IllegalArgumentException("expire time least gt zero");
        }
        if (timeout <= 0L) {
            throw new IllegalArgumentException("timeout time least gt zero");
        }
        final long deadline = System.nanoTime() + timeUnit.toNanos(timeout);
        boolean result;
        for (;;) {
            result = redisLock.lock(key, requestId, expire, timeUnit);
            if (result) {
                thread = Thread.currentThread();
                return true;
            } else {
                long remaining = deadline - System.nanoTime();
                if (remaining <= 0L) {
                    return false;
                }
                LockSupport.parkNanos(remaining);
            }
        }
    }

    @Override
    public boolean isHeldByCurrentThread() {
        return thread == Thread.currentThread();
    }

    @Override
    public boolean isLocked() {
        return redisLock.isLocked(key);
    }

    @Override
    public void unlock() {
        if (thread != Thread.currentThread()) {
            throw new IllegalMonitorStateException();
        }
        String field = getLockName(Thread.currentThread().getId());
        for (int i = 0; i <= unlockRetry; i++) {
            try {
                redisLock.unlock(key, field);
                break;
            } catch (Exception e) {
                log.error("当前线程解锁异常,线程ID:{},error:{}", Thread.currentThread().getId(), e.getMessage());
            }
            if (unlockRetry == i) {
                log.warn("当前线程解锁异常,线程ID:{}", Thread.currentThread().getId());
            }
        }
    }

    private String getLockName(long threadId) {
        return this.id + ":" + threadId;
    }
}
